package gapidt.nlp;

import edu.smu.tspell.wordnet.NounSynset;
import edu.smu.tspell.wordnet.Synset;
import edu.smu.tspell.wordnet.SynsetType;
import edu.smu.tspell.wordnet.VerbSynset;
import edu.smu.tspell.wordnet.WordNetDatabase;
import java.util.List;
import java.util.Vector;

import org.apache.log4j.Logger;

/**
 * Esta clase provee funciones para analizar textos en lenguaje natural en base 
 * a su significado.
 * 
 * @author Alejandro Durante
 */
public class SemanticAnalyzer {
	
	static Logger logger = Logger.getLogger(SemanticAnalyzer.class);
	
	public static final String HYPERNYMS_TREE_ROOT = "ROOT";
	
	public static final int AMBIGUITY_MIN_NAME_LENGTH = 1;
	//public static final int AMBIGUITY_MAX_NAME_LENGTH = 50;
	public static String [] ambiguousNames = {
		"param", "arg", "var", "obj", "object", 
		"foo", "input", "output", "in", "out", "str",
		"in0", "in1", "in2", "in3", "in4", "in5", "in6", "in7", "in8", "in9", "in10", 
		"out0", "out1", "out2", "out3", "out4", "out5", "out6", "out7", "out8", "out9", "out10",
		"arg0", "arg1", "arg2", "arg3", "arg4", "arg5", "arg6", "arg7", "arg8", "arg9", "arg10",
		"obj0", "obj1", "obj2", "obj3", "obj4", "obj5", "obj6", "obj7", "obj8", "obj9", "obj10",
		"str0", "str1", "str2", "str3", "str4", "str5", "str6", "str7", "str8", "str9", "str10" 
	};
	
	public static String[] getAmbiguousNames() {
		return ambiguousNames;
	}

	public static void setAmbiguousNames(String[] ambiguousNames) {
		SemanticAnalyzer.ambiguousNames = ambiguousNames;
	}

	public static boolean isAmbiguousName(String name){
		if(name == null || name.trim().length() == 0)
			return true;
		
		//1. Check name length
		int nameLength = name.trim().length();
		if(nameLength <= AMBIGUITY_MIN_NAME_LENGTH)
			return true;
		
		//2. Check if the name is blacklisted
		for(String ambiguousWord : ambiguousNames){
			if(name.toLowerCase().equals(ambiguousWord.toLowerCase()))
				return true;
		}
		
		return false;
	}
	
	public static boolean isAmbiguousNounName(String name){
		if(!isAmbiguousName(name)){
			
			List<Word> relevantWords = SemanticParser.getRelevantWords(SemanticUtils.camelCase2Human(name));
			
			if(relevantWords.size() > 0){
				int nounCount = 0;
				for(Word word : relevantWords){
					if(word.getType().startsWith("NN")){
						nounCount++;
					}
				}
				
				if(nounCount == 0){
					return true;
				}
			}
			
			return false;
		}else{
			return true;
		}
	}
	
	public static boolean isAmbiguousActionName(String name){
		
		if(!isAmbiguousName(name)){
			
			//Prepend "it must"
			name = "it must " + SemanticUtils.camelCase2Human(name);
			
			List<Word> relevantWords = SemanticParser.getRelevantWords(name);
			
			int nounCount = 0;
			int verbCount = 0;
			int adjectiveCount = 0;
			
			for(Word word : relevantWords){
				if(word.getType().startsWith("NN")){
					nounCount++;
				}else if(word.getType().startsWith("VB")){
					verbCount++;
				}else if(word.getType().startsWith("JJ")){
					adjectiveCount++;
				}
			}
			
			//If it has no nouns or verbs, it's considered ambiguous
			if(nounCount + adjectiveCount > 0 && verbCount > 0){
				return false;
			}else{
				return true;
			}
		}else{
			return true;
		}
	}
	
	/**
	 * Calcula el grado de similitud entre dos textos
	 * @return Grado de similitud, entre 0 y 1
	 */
	public static double similarityDegree(String text1, String text2){
		
		Tree<String> t1 = buildHypernymsTree(text1);
		Tree<String> t2 = buildHypernymsTree(text2);
		
		//System.out.println("TREE 1:");
		//System.out.println(t1.toString());
		
		//System.out.println("TREE 2:");
		//System.out.println(t2.toString());
		
		return similarityBetweenHypernymsTrees(t1, t2);
	}

	/**
	 * Calcula el grado de similitud entre dos árboles de hiperónimos 
	 * @return Grado de similitud, entre 0 y 1
	 */
	public static double similarityBetweenHypernymsTrees(Tree<String> t1, Tree<String> t2){
		
		Tree<String> commonHypernymsSubtree = new Tree<String>(HYPERNYMS_TREE_ROOT);
		
		getCommonHypernymsSubtree(t1, t2, commonHypernymsSubtree);
		
		double maxNodeCount = Math.max((double) t1.getNodeCount(), (double) t2.getNodeCount());
		
		return (double) commonHypernymsSubtree.getNodeCount() / maxNodeCount;
	}
	
	/**
	 * Obtiene el mayor subárbol que tienen en común los árboles t1 y t2
	 */
	public static void getCommonHypernymsSubtree(Tree<String> t1, Tree<String> t2, Tree<String> commonSubtree){
		for(Tree<String> child1 : t1.getChilds()){
			for(Tree<String> child2 : t2.getChilds()){
				if(child1.getData().equals(child2.getData())){
					Tree<String> copy = new Tree<String>(HYPERNYMS_TREE_ROOT);
					copy.setData(child1.getData());
					commonSubtree.addChild(copy);
					getCommonHypernymsSubtree(child1, child2, copy);
				}
			}
		}
	}
	
	/**
	 * Construye el árbol de hiperónimos de un texto dado
	 */
	private static Tree<String> buildHypernymsTree(String text){
		
		List<Word> relevantWords = SemanticParser.getRelevantWords(text);
		
		Tree<String> hypernymsTree = new Tree<String>(HYPERNYMS_TREE_ROOT);
		WordNetDatabase database = WordNetDatabase.getFileInstance();
		
		for(Word word : relevantWords){
			
			SynsetType type = SynsetType.NOUN;
			if(word.getType().startsWith("VB"))
				type = SynsetType.VERB;
			else if(word.getType().startsWith("JJ"))
				type = SynsetType.ADJECTIVE;
			
			Synset[] allConcepts = database.getSynsets(word.getWord(), type);
			
			for(Synset syn : allConcepts){
				Tree<String>[] hypermyns = hypermynToTree(syn);
				if(hypermyns != null)
					for(Tree<String> hy : hypermyns)
						hypernymsTree.mergeChilds(hy);
			}
		}
		
		return hypernymsTree;
	}
	
	/**
	 * Crea un meaning tree de nouns
	 * 
	 * @param s
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private static Tree<String>[] hypermynToTree(NounSynset s, Vector<Synset> callControl){
		if(callControl.contains(s)){
			Tree<?>[] array = new Tree<?>[0];
			return (Tree<String>[])array;
		}
		callControl.add(s);
		Tree<String> result = new Tree<String>(HYPERNYMS_TREE_ROOT);
		Tree<String> newNode = new Tree<String>(stringArrayToString(s.getWordForms()));
		//Si es raiz entonces es un arbol de un solo nodo
		if(s.getHypernyms().length == 0)
			result.addChild(newNode);
		else{
			//por cada hypernyms crea un conjunto de �rbol
			for(NounSynset n : s.getHypernyms()){
				Tree<String>[] a = hypermynToTree(n,callControl);
				//por cada conjunto de arboles se lo une al principal
				for(Tree<String> child : a)
					result.mergeChilds(child);
			}
			//a todas las hojas del �rbol resultado se le agrega la nueva hoja de este paso del algoritmo
			Vector<Tree<String>> leaves = result.getLeaves();
			for(Tree<String> leaf : leaves)
				leaf.addChild(newNode);
		}
		//Se divide el �rbol artificial con el nodo raiz ALL en un conjunto verdadero de arboles hijos a retornar
		Tree<?>[] array = new Tree<?>[result.getChilds().size()];
		for(int i = 0; i < array.length; i++){
			Tree<String> tree = result.getChilds().elementAt(0);
			result.removeChild(tree);
			array[i] = tree;
		}
		callControl.remove(callControl.size()-1);
		return (Tree<String>[])array;
	}
	
	/**
	 * Crea un meaning tree de un verbos
	 */
	@SuppressWarnings("unchecked")
	private static Tree<String>[] hypermynToTree(VerbSynset s,Vector<Synset> callControl){
		if(callControl.contains(s)){
			Tree<?>[] array = new Tree<?>[0];
			return (Tree <String>[])array;
		}
		callControl.add(s);
		Tree<String> result = new Tree<String>(HYPERNYMS_TREE_ROOT);
		Tree<String> newNode = new Tree<String>(stringArrayToString(s.getWordForms()));
		//Si es raiz entonces es un arbol de un solo nodo
		if(s.getHypernyms().length == 0)
			result.addChild(newNode);
		else {
			//por cada hypernyms crea un conjunto de �rbol
			for(VerbSynset n : s.getHypernyms()){
				Tree<String>[] a = hypermynToTree(n,callControl);
				//por cada conjunto de arboles se lo une al principal
				for(Tree<String> child : a)
					result.mergeChilds(child);
			}
			//a todas las hojas del �rbol resultado se le agrega la nueva hoja de este paso del algoritmo
			Vector<Tree<String>> leaves = result.getLeaves();
			for(Tree<String> leaf : leaves)
				leaf.addChild(newNode);
		}
		//Se divide el �rbol artificial con el nodo raiz ALL en un conjunto verdadero de arboles hijos a retornar
		Tree<?>[] array = new Tree<?>[result.getChilds().size()];
		for(int i = 0; i < array.length; i++) {
			Tree<String> tree = result.getChilds().elementAt(0);
			result.removeChild(tree);
			array[i] = tree;
		}
		callControl.remove(callControl.size()-1);
		return (Tree<String>[])array;
	}
	
	/**
	 * Crea un "meaning tree" de una palabra válida
	 * Caso contrario retorna null.
	 * 
	 * @param s Palabra válida
	 * @return un Meaning tree o null 
	 */
	public static Tree<String>[] hypermynToTree(Synset s){
		Tree<String>[] result = null;
		Vector<Synset> callControl = new Vector<Synset>();
		if(s.getType().equals(SynsetType.NOUN))
			result = hypermynToTree((NounSynset)s,callControl);
		else 
			if(s.getType().equals(SynsetType.VERB))
				result = hypermynToTree((VerbSynset)s,callControl);
		return result;
	}
	
	private static String stringArrayToString(String[] array){
		StringBuffer buffer = new StringBuffer();
		for(int i = 0; i < array.length; i++) {
			buffer.append(array[i]);
			if(i != array.length - 1)
				buffer.append(", ");
		}
		return buffer.toString();
	}
}
